package tech.heptagram.staff.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.stuxuhai.jpinyin.PinyinException;

import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import tech.heptagram.core.common.util.JsonUtil;
import tech.heptagram.staff.domain.StaffExt;
import tech.heptagram.staff.domain.StaffLog;
import tech.heptagram.staff.domain.StaffUser;
import tech.heptagram.staff.exception.StaffApiException;
import tech.heptagram.staff.mapper.DingtaskMapper;
import tech.heptagram.staff.mapper.WxTaskMapper;
import tech.heptagram.staff.mapper.StaffExtMapper;
import tech.heptagram.staff.mapper.StaffMapper;
import tech.heptagram.staff.model.DingtalkTask;
import tech.heptagram.staff.model.Staff;
import tech.heptagram.staff.model.StaffAuthException;
import tech.heptagram.staff.model.WxTask;
import tech.heptagram.staff.model.enums.JobTypeEnum;
import tech.heptagram.staff.model.enums.StaffType;
import tech.heptagram.staff.query.StaffQuery;
import tech.heptagram.staff.query.StaffQueryBuilder;
import tech.heptagram.staff.query.StaffRemoveReq;
import tech.heptagram.staff.query.StaffSaveReq;
import tech.heptagram.staff.service.*;
import tech.heptagram.staff.util.AssertUtils;
import tech.heptagram.staff.util.PinyinUtils;

/**
 * Staff Service
 *
 * @author wang.yuchuan
 * @date 2018/8/30
 */
@Service
@Slf4j
@CacheConfig(cacheNames = {"staffs"})
public class StaffServiceImpl implements StaffService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private StaffMapper staffMapper;

    @Resource
    private StaffUserService staffUserService;

    @Resource
    private DepartmentStaffService departmentStaffService;

    @Resource
    private SequenceService sequenceService;

    @Resource
    private StaffLogService staffLogService;

    @Resource
    private StaffExtMapper staffExtMapper;

    @Resource
    private DingtaskMapper dingtaskMapper;

    @Resource
    private WxTaskMapper wxTaskMapper;

    @Override
    public List<Staff> findStaffs(StaffQuery query) {
        if (Objects.isNull(query)) {
            return new ArrayList<>();
        }
        return staffMapper.selectStaffs(query);
    }

    @Override
    public List<Staff> findByStaffCodes(List<String> staffCodes) {
        if (CollectionUtils.isEmpty(staffCodes)) {
            return new ArrayList<>();
        }
        StaffQuery staffQuery = StaffQueryBuilder.builder().staffCodes(staffCodes).build();
        return findStaffs(staffQuery);
    }

    @Override
    public Staff getStaff(StaffQuery staffQuery) {
        if (Objects.isNull(staffQuery)) {
            return null;
        }

        return staffMapper.selectStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':code:'+ #staffCode", unless = "#result == null")
    public Staff getByCode(String staffCode) {
        StaffQuery staffQuery = StaffQueryBuilder.builder().staffCode(staffCode).build();
        return getStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':staffName:'+ #staffName", unless = "#result == null")
    public Staff getByName(String staffName) {
        StaffQuery staffQuery = StaffQueryBuilder.builder().staffName(staffName).build();
        return getStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':mobile:'+ #mobile", unless = "#result == null")
    public Staff getByMobile(String mobile) {
        StaffQuery staffQuery = StaffQueryBuilder.builder().mobile(mobile).build();
        return getStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':wxId:'+ #wxUserId", unless = "#result == null")
    public Staff getByWxUserId(String wxUserId) {
        StaffQuery staffQuery = StaffQueryBuilder.builder().wxUserId(wxUserId).build();
        return getStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':dingtalkId:'+ #dingtalkId", unless = "#result == null")
    public Staff getByDingtalkId(String dingtalkId) {
        StaffQuery staffQuery = StaffQueryBuilder.builder().dingtalkId(dingtalkId).build();
        return getStaff(staffQuery);
    }

    @Override
    @Cacheable(key = "caches[0].name + ':nickName:'+ #nickName", unless = "#result == null")
    public Staff getByNickName(String nickName) {
        Staff staff = staffMapper.selectByNickName(nickName);
        return staff;
    }

    /**
     * 根据部门ID查询员工列表
     *
     * @param departmentId 部门ID
     * @return 员工列表
     */
    @Override
    public List<Staff> getByDepartmentId(Integer departmentId) {
        String key = "staffApi2:getStaffByDepartmentId:departmentId:" + departmentId;
        String redisValue = stringRedisTemplate.opsForValue().get(key);
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(redisValue)) {
            log.info("从redis中获取到departmentId:{} 的staff数据, redis key: {}", departmentId, key);
            try {
                List<Staff> list = JsonUtil.fromJson(redisValue, new TypeReference<List<Staff>>() {});
                return list;
            } catch (StaffApiException e) {
                log.error("从redis获取到 staffList 解析失败,redisKey:{} ", key, e);
            }
        }
        List<Staff> list = staffMapper.selectByDepartmentId(departmentId);
        try {
            // 缓存一天
            stringRedisTemplate.opsForValue().set(key, JsonUtil.toJson(list), 1, TimeUnit.DAYS);
        } catch (StaffApiException e) {
            log.error("向redis添加缓存失败, redisKey:{}", key, e);
        }
        return list;
    }

    /**
     * 根据部门ID列表查询员工CODE列表
     *
     * @param deptIdList 部门ID列表
     * @return 员工CODE列表
     */
    @Override
    public List<String> getStaffCodeListByDeptIdList(List<Integer> deptIdList) {
       return staffMapper.selectStaffCodeListByDeptIdList(deptIdList);
    }

    @Override
    @Transactional
    public String saveStaff(StaffSaveReq req) {
        Staff staff = req.getStaff();
        AssertUtils.notNull(staff,"保存员工不能为空");
        AssertUtils.notNull(staff.getMobile(),"手机号不能为空");
        AssertUtils.notNull(staff.getCityId(),"城市编码不能为空");
        AssertUtils.notNull(staff.getCreatedBy(),"创建人不能为空");
        AssertUtils.notNull(staff.getTrueName(),"真实名称不能为空");
        if (staff.getMobile().length()<11 || staff.getMobile().length()>11){
            throw new StaffApiException("手机号位数不正确");
        }
        if (req.getDepartmentIds().isEmpty()){
            throw new StaffApiException("部门不能为空");
        }

        log.info("1、校验添加的数据：{}",staff);
        checkExistStaff(staff.getMobile(),staff.getEmail(),staff.getNickName(),staff.getMobile2());

        log.info("2、根据电话号码{} 生成员工编号",staff.getMobile());
        String code = makeStaffCode(staff.getMobile());
        staff.setCode(code);

        log.info("3、正式员工获取工号",staff.getType());
        if (staff.getJobNo() == null && StaffType.FORMAL.getValue() == staff.getType()){
            String jobNum = sequenceService.generateStaffJobNo();
            staff.setJobNo(Integer.parseInt(jobNum));
        }

        log.info("4、生成邮箱和拼音：{}",staff);
        try {
            Staff resp = changePinIn(staff.getTrueName());
            staff.setNamePinyin(resp.getNamePinyin());
            if (StringUtils.isEmpty(staff.getEmail())) {
                staff.setEmail(resp.getEmail());
            }
        } catch (PinyinException e) {
            throw new StaffApiException("生成邮箱和拼音出错");
        }

        log.info("5、保存员工信息：{}",staff);
        staffMapper.insert(staff);

        StaffUser staffUser = new StaffUser();
        staffUser.setStaffCode(code);
        staffUser.setCreatedBy(staff.getCreatedBy());
        staffUser.setModifiedBy(staff.getModifiedBy());
        log.info("6、保存员工用户信息：{}",staffUser);
        staffUserService.saveStaffUser(staffUser);

        log.info("7、保存员工与部门关系");
        departmentStaffService.saveOrUpdate(req.getDepartmentIds(),staff.getCode(),staff.getCreatedBy());

        log.info("8、添加staffLog日志");
        saveStaffLog(staff.getCreatedBy(), code,"添加了该员工");

        StaffExt staffExt = new StaffExt();
        BeanUtils.copyProperties(staff,staffExt);
        staffExt.setStaffId(staff.getId());
        if (Objects.nonNull(staff.getType()) && Objects.nonNull(StaffType.valueOf(staff.getType()))) {
            staffExt.setStaffType(StaffType.valueOf(staff.getType()).getDesc());
        }
        log.info("9、保存员工扩展信息：{}",staffExt);
        staffExtMapper.insertStaffExt(staffExt);

        if(Objects.nonNull(req.getSync2Dingtalk()) && req.getSync2Dingtalk()) {
            log.info("10.1、添加新增员工到钉钉同步任务");
            saveDingTask(JobTypeEnum.CREATE_USER,staff.getCode());
        }
        if(Objects.nonNull(req.getSync2WorkWx()) && req.getSync2WorkWx()) {
            log.info("10.2、添加新增员工到企业微信同步任务");
            saveWxTask(JobTypeEnum.CREATE_USER, staff.getCode());
        }
        return staff.getCode();
    }

    @Override
    @Transactional
    @CacheEvict(allEntries = true)
    public void updateStaff(StaffSaveReq req) {
        Staff staff = req.getStaff();
        AssertUtils.notNull(staff,"修改的员工信息不能为空");
        AssertUtils.notNull(staff.getCode(),"员工编号不能为空");
        AssertUtils.notNull(req.getDepartmentIds(),"员工部门列表不能为空");
        AssertUtils.notNull(staff.getModifiedBy(),"修改人不能为空");
        log.info("1、修改员工和部门关系");
        departmentStaffService.saveOrUpdate(req.getDepartmentIds(),staff.getCode(),staff.getModifiedBy());

        log.info("2.1、校验修改的数据");
        checkCanUpdateStaff(req.getStaff());

        log.info("2.2、修改员工基本信息");
        staffMapper.updateStaff(req.getStaff());

        log.info("2.3、修改员工扩展信息");
        StaffExt staffExt = staffExtMapper.selectByStaffId(staff.getId());
        if (Objects.isNull(staffExt)){
            staffExt = new StaffExt();
            BeanUtils.copyProperties(staff,staffExt);
            if (Objects.nonNull(staff.getType()) && Objects.nonNull(StaffType.valueOf(staff.getType()))) {
                staffExt.setStaffType(StaffType.valueOf(staff.getType()).getDesc());
            }
            staffExt.setStaffId(staff.getId());
            staffExtMapper.insertStaffExt(staffExt);
        }else{
            BeanUtils.copyProperties(staff,staffExt);
            staffExtMapper.updateByStaffId(staffExt);
        }

        log.info("3、添加staffLog日志");
        saveStaffLog(staff.getModifiedBy(),staff.getCode(),"修改了该员工");

        if(Objects.nonNull(req.getSync2Dingtalk()) && req.getSync2Dingtalk()) {
            log.info("4.1、添加修改员工到钉钉同步任务");
            saveDingTask(JobTypeEnum.UPDATE_USER,staff.getCode());
        }
        if(Objects.nonNull(req.getSync2WorkWx()) && req.getSync2WorkWx()) {
            log.info("4.2、添加修改员工到企业微信同步任务");
            saveWxTask(JobTypeEnum.UPDATE_USER, staff.getCode());
        }

    }

    @Override
    @CacheEvict(allEntries = true)
    public void updateStaffStatus(StaffRemoveReq req) {
        AssertUtils.notNull(req.getCode(),"用户编号不能为空");
        AssertUtils.notNull(req.getStatus(),"用户状态不能为空");
        AssertUtils.notNull(req.getModifiedBy(),"修改人不能为空");
        AssertUtils.notNull(req.getDimissionTime(),"离职时间不能为空");

        log.info("1、修改员工为离职状态");
        staffMapper.updateStatus(req.getCode(),req.getStatus(),req.getDimissionTime(),req.getModifiedBy());

        log.info("2、保存员工日志记录");
        saveStaffLog(req.getModifiedBy(),req.getCode(),"设置了员工为离职状态");

        if(Objects.nonNull(req.getSync2Dingtalk()) && req.getSync2Dingtalk()) {
            log.info("3.1、添加删除员工到钉钉同步任务");
            saveDingTask(JobTypeEnum.DELETE_USER,req.getCode());
        }
        if(Objects.nonNull(req.getSync2WorkWx()) && req.getSync2WorkWx()) {
            log.info("3.2、添加删除员工到企业微信同步任务");
            saveWxTask(JobTypeEnum.DELETE_USER, req.getCode());
        }
    }

    private Boolean checkNamePinyin(String tempNamePinyin) {
        //查询namePinyin是否存在（包括离职的）
        Staff staffPoNamePinyin = existNamePinyin(tempNamePinyin);
        if(staffPoNamePinyin != null){
            return true;
        }
        return false;
    }

    @Override
    public PageInfo<Staff> findStaffsWithPage(StaffQuery query) {
        if (Objects.isNull(query)) {
            return new PageInfo<>(new ArrayList<>());
        }

        if (Objects.nonNull(query.getPage()) && Objects.nonNull(query.getPageSize())) {
            PageHelper.startPage(query.getPage(), query.getPageSize());
        }

        List<Staff> staffs = findStaffs(query);

        return new PageInfo<>(staffs);
    }

    @Override
    public DepartmentService getDepartmentService() {
        return null;
    }

    @Override
    public BpmsProcessService getBpmsProcessService() {
        return null;
    }

    @Override
    public ProcessRecordService getProcessRecordService() {
        return null;
    }


    private void saveDingTask(JobTypeEnum jobTypeEnum, String staffCode){
        DingtalkTask dingTaskPo = new DingtalkTask();
        dingTaskPo.setType(jobTypeEnum.getValue());

        HashMap<String,String> message = new HashMap<>();
        message.put("staffCode",staffCode);
        dingTaskPo.setMessage(JsonUtil.toJson(message));

        dingtaskMapper.insert(dingTaskPo);
    }
    private void saveWxTask(JobTypeEnum jobTypeEnum, String staffCode){
        WxTask wxTask = new WxTask();
        wxTask.setType(jobTypeEnum.getValue());
        HashMap<String,String> message = new HashMap<>();
        message.put("staffCode",staffCode);
        wxTask.setMessage(JsonUtil.toJson(message));
        wxTaskMapper.insert(wxTask);
    }

    /**
     * 添加staffLog日志
     * @param person
     * @param code
     * @param text
     */
    private void saveStaffLog(String person, String code, String text) {
        //记录日志
        StaffLog staffLog = new StaffLog();
        staffLog.setStaffLogPerson(person);
        staffLog.setStaffCode(code);
        staffLog.setStaffLogText(text);
        staffLogService.saveStaffLog(staffLog);
    }


    @Override
    public Staff auth(String account, String password) throws StaffAuthException {
        if (StringUtils.isEmpty(account)) {
            throw new StaffAuthException("账号为空");
        }
        if (StringUtils.isEmpty(password)) {
            throw new StaffAuthException("密码为空");
        }

        Staff staff = staffMapper.selectByAuthKey(account);
        if (Objects.isNull(staff)) {
            throw new StaffAuthException("员工身份认证失败, 不存在此用户");
        }

        boolean authResult = staffUserService.auth(staff.getCode(), password);
        if (!authResult) {
            throw new StaffAuthException("员工身份认证失败，密码或用户名不正确");
        }
        return staff;
    }

    @Override
    @CacheEvict(allEntries = true)
    public void updateStaffNickname(String staffCode, String nickName) {
        if (StringUtils.isEmpty(staffCode)) {
            throw new StaffApiException("员工编码不能为空");
        }
        if (StringUtils.isEmpty(nickName)) {
            throw new StaffApiException("员工花名不能为空");
        }
        Staff sameNicknameStaff = getByNickName(nickName);
        if (Objects.nonNull(sameNicknameStaff)) {
            throw new StaffApiException("花名" + nickName + " 已被他人占用");
        }
        staffMapper.updateNickname(staffCode, nickName);
    }

    private void checkExistStaff(String mobile,String emaile,String nickName,String mobile2) {
        if (existMobile(mobile) != null) {
            throw new StaffApiException("电话号码已存在");
        }
        if (existEmail(emaile) != null){
            throw new StaffApiException("邮箱号码已存在");
        }
        if (existNickName(nickName) != null) {
            throw new StaffApiException("花名已存在");
        }
        if(existMobile2(mobile2) != null){
            throw new StaffApiException("公司手机号已存在");
        }
    }

    /**
     * 查询花名是否存在（不包括离职的）
     * @param nickName
     * @return
     */
    private Staff existNickName(String nickName) {
        if (org.apache.commons.lang3.StringUtils.isEmpty(nickName)){
            return null;
        }
        Staff nickNamePo = new Staff();
        nickNamePo.setNickName(nickName);
        nickNamePo.setStatus(1);
        return staffMapper.selectStaffForChecking(nickNamePo);
    }
    /**
     * 查询公司手机号是否存在（不包括离职的）
     * @param mobile2
     * @return
     */
    private Staff existMobile2(String mobile2){
        if(org.apache.commons.lang3.StringUtils.isEmpty(mobile2)){
            return null;
        }
        Staff mobile2Po = new Staff();
        mobile2Po.setMobile2(mobile2);
        mobile2Po.setStatus(1);
        return staffMapper.selectStaffForChecking(mobile2Po);

    }
    /**
     * 查询email是否存在（包括离职的）
     * @param emaile
     * @return
     */
    private Staff existEmail(String emaile) {
        if (org.apache.commons.lang3.StringUtils.isEmpty(emaile)){
            return null;
        }
        Staff emailPo = new Staff();
        emailPo.setEmail(emaile);
        return staffMapper.selectStaffForChecking(emailPo);
    }
    /**
     *查询手机号是否存在（不包括离职的）
     * @param mobile
     * @return
     */
    private Staff existMobile(String mobile) {
        Staff phonePo = new Staff();
        phonePo.setMobile(mobile);
        phonePo.setStatus(1);
        return staffMapper.selectStaffForChecking(phonePo);
    }

    /**
     * 查询namePinyin是否存在 （包括离职的）
     * @param tempNamePinyin
     * @return
     */
    private Staff existNamePinyin(String tempNamePinyin){
        Staff namePinyinPo = new Staff();
        namePinyinPo.setNamePinyin(tempNamePinyin);
        //根据namePinyin查询员工
        Staff NamePinyinPoResult  = staffMapper.selectStaffForChecking(namePinyinPo);
        if (NamePinyinPoResult != null){
            return NamePinyinPoResult;
        }
        return null;
    }

    /**
     * 修改时检验数据
     * @param req
     */
    private void checkCanUpdateStaff(Staff req) {
        StaffQuery query = new StaffQuery();
        query.setStaffCode(req.getCode());
        Staff findPo = staffMapper.selectStaff(query);
        if (!findPo.getMobile().equals(req.getMobile())){
            if (existMobile(req.getMobile()) != null) {
                log.info("电话号码已存在: {}", req.getMobile());
                throw new StaffApiException("电话号码已存在");
            }
        }
        if (!findPo.getMobile2().equals(req.getMobile2())){
            if (existMobile2(req.getMobile2()) != null) {
                log.info("公司手机号已存在: {}", req.getMobile2());
                throw new StaffApiException("公司手机号已存在");
            }
        }
        if (!findPo.getNickName().equals(req.getNickName())){
            if (existNickName(req.getNickName()) != null) {
                log.info("花名已存在: {}", req.getNickName());
                throw new StaffApiException("花名已存在");
            }
        }
        if(!findPo.getEmail().equals(req.getEmail())){
            if (existEmail(req.getEmail()) != null){
                log.info("邮箱号码已存在: {}", req.getEmail());
                throw new StaffApiException("邮箱号码已存在");
            }
        }

        if(findPo.getJobNo() == null &&  StaffType.FORMAL.getValue() == req.getType()){
            String jobNum = sequenceService.generateStaffJobNo();
            req.setJobNo(Integer.parseInt(jobNum));
        }
        if (findPo.getJobNo() != null){
            req.setJobNo(null);
        }
    }

    private String makeStaffCode(String mobile) {
        return String.valueOf(new Date().getTime()/1000) + mobile.substring(1,11);
    }

    /**
     * 生成拼音和邮箱的方法
     */
    private Staff changePinIn(String trueName) throws PinyinException {
        trueName = trueName.trim();
        if (org.apache.commons.lang3.StringUtils.isEmpty(trueName)) {
            return null;
        }
        Staff resp = new Staff();
        // 生成拼音
        String tempNamePinyin = PinyinUtils.generateStaffNamePinyin(trueName);
        //判断参数对象中code的值
        //若code == null
        //判断拼音是否存在
        Boolean TFtemp = checkNamePinyin(tempNamePinyin);
        Integer num = 2;
        //若拼音存在  记录num的值
        Integer flagNum = 0;
        while (TFtemp) {
            //存在
            tempNamePinyin = PinyinUtils.generateStaffNamePinyin(trueName, num);
            TFtemp = checkNamePinyin(tempNamePinyin);
            flagNum = num;
            num++;
        }
        resp.setNamePinyin(tempNamePinyin);
        //生成email
        //判断flagNum的值  若 flagNum<2  则不需要拼接
        if (flagNum < 2) {
            flagNum = null;
        }
        String tempEmail = PinyinUtils.generateStaffEmail(trueName, flagNum);
        resp.setEmail(tempEmail);
        return resp;
    }
}
